#include<pthread.h>
#include<stdio.h>
#include<cassert>
#include<cstdlib>
#include<vector>
#include<utility>
#include <fstream> 
#include <sstream> 
#include <string> 
#include <list>
#include "RWLock.h"

#define LECTURA 0
#define ESCRITURA 1
using namespace std;

int recurso;
int orden;
int cant_threads;
int cant_op;

ofstream file;

sem_t mutexOrden;

RWLock lock;
//~ RWLock lockTiempo;

struct testInfo {
	int ordenT; //orden
	int metodo; // 0= lee y 1=escribe
	int valor; //valor del leer/escribir
	int pid; //valor del Nro de thread
	
	std::string toString(){
		std::stringstream ss;
		ss << "[Orden: " << ordenT << ";Metodo: " << metodo << ";Valor: " << valor << ";Pid: " << pid << "]\n";
		std::string s = ss.str();
	return s;
	}
	
	void write(){
		
		file << toString();
	}
	
};

vector< vector <testInfo> > threads_logs;
vector<pair<int,int> > CountRWThread(cant_threads);  

void guardarHistorial( vector<list<testInfo> > historial){
	for(int i=0;i< historial.size(); i++){
		list<testInfo>::iterator iter;
		for (iter = historial[i].begin() ; iter != historial[i].end(); iter++){
			iter->write();
		}
	}
	file.close();
}

void rVariableCompartida(unsigned int pid, int pos)
{
	lock.rlock();
	
	if(pos >= 0){
		threads_logs[pid][pos].ordenT=orden;
		threads_logs[pid][pos].valor=recurso;
		threads_logs[pid][pos].metodo=0;
		threads_logs[pid][pos].pid=pid;
	}

	sem_wait(&mutexOrden);
		orden++;
	sem_post(&mutexOrden);
	 sleep(1);
	lock.runlock();
}

void wVariableCompartida(unsigned int pid,int pos)
{
	lock.wlock();
	
	recurso++;
	if (pos >= 0){
		threads_logs[pid][pos].ordenT=orden;
		threads_logs[pid][pos].valor=recurso;
		threads_logs[pid][pos].metodo=1;
		threads_logs[pid][pos].pid=pid;
	}

	orden++;
	sleep(1);
	lock.wunlock();
}	
	
bool verificarCantLecturaEscritura(int cant){
	int lectura;
	int escritura;
	bool result=true;
		
	for(int i=0; i<cant && result ; i++){
		lectura=0;
		escritura=0;
		for(int j=0; j<cant_op; j++){
			if(threads_logs[i][j].metodo==0){
				lectura++;
			}else{
				escritura++;
			}
		
		}
		
		result = (CountRWThread[i].first == lectura)	&& (CountRWThread[i].second == escritura);
	}
	
	return result;
}	

int contar(int metodo, list<testInfo> lista){
   list<testInfo>::iterator iter;
	int result = 0;
    for (iter = lista.begin() ; iter != lista.end(); iter++){
		if(iter->metodo == metodo) result++;
    }
    return result;
}

bool lecturasIguales(list<testInfo> lista){
	list<testInfo>::iterator iter;
	bool result = true;
	int valor = lista.front().valor;
    for (iter = lista.begin() ; iter != lista.end() && result; iter++){
		result = result && iter->valor == valor;
    }
    return result;
}

bool validarTiempoT(list<testInfo> lista){
	bool result = false;
	int countEsc = contar(ESCRITURA, lista);
	int cantidadLecturas = contar(LECTURA, lista);
	if(!(lista.size() > 1 &&  countEsc > 1)){
		if (!(lista.size() == 1 &&  countEsc == 1)){
			//Significa que son solo lecturas
			bool todosIguales = lecturasIguales(lista);
			result = cantidadLecturas == lista.size() && todosIguales;
		} else {
			//True porque es solo una escritura, la consistencia de la escritura se chequea despues.
			result = true;
		}
	}
	return result;
}

bool verificarHistorial(vector<list<testInfo> > historial){
	bool validacionHorizontal = true;
	for(int i = 0; i < historial.size() && validacionHorizontal; i++){
		 validacionHorizontal = validacionHorizontal && validarTiempoT(historial[i]);
	}
	
	bool validacionVertical = true;
	if(validacionHorizontal){
		
		for(int i=0; i < historial.size()-1 && validacionVertical ; i++){
			
			if(historial[i].front().metodo == ESCRITURA && historial[i+1].front().metodo == ESCRITURA){
				validacionVertical = validacionVertical && (historial[i].front().valor + 1 == historial[i+1].front().valor);
			}else if(historial[i].front().metodo == ESCRITURA && historial[i+1].front().metodo == LECTURA ){
				validacionVertical = validacionVertical && (historial[i].front().valor == historial[i+1].front().valor);
			
			}else if(historial[i].front().metodo == LECTURA && historial[i+1].front().metodo == ESCRITURA ){
				validacionVertical = validacionVertical && (historial[i].front().valor + 1 == historial[i+1].front().valor);
			}else if(historial[i].front().metodo == LECTURA && historial[i+1].front().metodo == LECTURA){
				validacionVertical = validacionVertical && (historial[i].front().valor == historial[i+1].front().valor);
			}
		}
	}

	return validacionHorizontal && validacionVertical;
}


void validarResultados(int cant){
	assert(verificarCantLecturaEscritura(cant));
	
	vector<list<testInfo> > historialDeAccesos(orden);
	
	for(int j=0; j<cant; j++){
		for(int k=0;k<cant_op;k++){
			
			int pos =threads_logs[j][k].ordenT;
			historialDeAccesos[pos].push_back(threads_logs[j][k]);
		}
	}	
	
//	guardarHistorial(historialDeAccesos);
	
	assert(verificarHistorial(historialDeAccesos));
}

void *EscribeYLeeAleatorio(void *p_minumero){
	unsigned int minumero = *((int *) p_minumero);
	
	for(int i=0; i< cant_op; i++){
		
		if(rand() %2==0){
			rVariableCompartida(minumero, i);
			CountRWThread[minumero].first++;
			
		}else{
			wVariableCompartida (minumero,i);	
			CountRWThread[minumero].second++;
		}
		
	}
}

void testEscribirYleerVariosThreads(unsigned int cant)
{
	threads_logs.resize(cant); 	

	for(int i=0; i<cant; i++){
		for(int j=0; j<cant_op; j++){
			testInfo test;
			test.ordenT = -1;
			test.metodo = -1;
			test.valor = -1;
			test.pid = -1;
			threads_logs[i].push_back(test)	;
		}
	}
	
    CountRWThread.resize(cant); 
	for(int i=0;i<cant;i++){
		pair<int,int> temp(0,0);
		CountRWThread[i] = temp;
	}
	
    pthread_t thread[cant];
    int tids[cant], tid;
	for (tid = 0; tid < cant; ++tid) {
         tids[tid] = tid;
         pthread_create(&thread[tid], NULL, EscribeYLeeAleatorio, &tids[tid]);
    }
 
    for (tid = 0; tid < cant; ++tid){
         pthread_join(thread[tid], NULL);
		 
	}
	
	validarResultados(cant);
}


int main(int argc, char* argv[])
{
	sem_init(&mutexOrden, 0, 1);
	
	srand (time(NULL));
	file.open("Log_thread.txt", ios::out | ios::app);
	
	RWLock lock();
	RWLock lockTiempo();
	
	cant_threads = atoi(argv[1]); 
	cant_op = atoi(argv[2]); 
	int cant_ejec = atoi(argv[3]); 

	cout << "Iniciando tests con " << cant_threads << " threads, " << cant_op << " operaciones, " << cant_ejec << " ejecuciones." << endl;

	for(int i = 0; i < cant_ejec;  i++){
		cout << "Iniciando ejecucion " << i << "...";
		recurso = 0;
		orden = 0;
		testEscribirYleerVariosThreads(cant_threads);
//		cout << "Ejecucion " << i << " finalizo exitosamente. " << endl;
		cout << "OK!" << endl;
	}
        
	file.close();
	cout << "Todos los tests finalizaron correctamente." << endl;
    return 0;
}
